/*
 * #%L
 * java model
 * %%
 * Copyright (C) 2009 - 2011 prefabware.com Stefan Isele
 * %%
 * All Rights Reserved.
 * This program is dual-licensed under the GNU General Public license
 * or a commercial license.
 * You may select to use one or the other of these licenses.
 * To obtain a commercial license please contact Stefan Isele at www.stefan-isele.de
 * Any modifications to this file must keep this entire header intact.
 * Redistribution, except as permitted by the selected license, is prohibited.
 * #L%
 */
package com.prefabware.jmodel.beans;

import java.lang.reflect.Method;
import java.util.List;

import com.prefabware.commons.Condition;
import com.prefabware.commons.JavaUtil;
import com.prefabware.commons.QualifiedName;
import com.prefabware.commons.collection.Filter;
import com.prefabware.jmodel.JClass;
import com.prefabware.jmodel.JClassDeclaration;
import com.prefabware.jmodel.JField;
import com.prefabware.jmodel.JMethod;
import com.prefabware.jmodel.JMethodDeclaration;
import com.prefabware.jmodel.JModifiers;
import com.prefabware.jmodel.JType;
import com.prefabware.jmodel.JTypeDeclaration;
import com.prefabware.jmodel.JTypeUtil;
import com.prefabware.jmodel.JVisibility;
import com.prefabware.jmodel.typefactory.JClassTypeFactoryDelegee;
import com.prefabware.jmodel.typefactory.JTypeClassVisitor;
import com.prefabware.jmodel.typefactory.TypeFactory;

/**
 * creates new Beans only for those types that were registered to this factory
 * 
 * @author sisele_job
 * 
 */
public class BeanTypeFactoryDelegee extends JClassTypeFactoryDelegee {
	private class BeanClassVisitor extends
			JClassTypeFactoryDelegee.JClassClassVisitor {

		public BeanClassVisitor(TypeFactory typeFactory, JType[] typeArguments) {
			super(typeFactory, typeArguments);
		}

		@Override
		public Bean getJType() {
			return (Bean) super.getJType();
		}

		@Override
		public JMethod newJMethod(Method method, JType declaringType,
				String name, JType returnType, JModifiers modifiers,
				JVisibility visibility, List<JType> typeArguments) {
			JClass declaringClass = (JClass) declaringType;
			JMethodDeclaration jMemberDeclaration = JTypeUtil
					.newMethodDeclaration(name, returnType, declaringType, modifiers,
							visibility, typeArguments.toArray(new JType[0]));
			//if its a getter use create a Getter instead of a JMethod
			if (JavaUtil.isGetter(method)) {
				String fieldName;
				fieldName = JavaUtil.getGetterFieldName(name,
						JTypeUtil.isBoolean(returnType));
				if (fieldName != null) {
					// methodname is not a getter name
					JField field = declaringClass.getField(fieldName);
					if (field != null) {
						// no field with that name, cannot be a getter
						if (field.getDeclaration().getType()
								.equals(returnType)) {
							// if the field has a different type than the returntype of the
							// method, cannot be a getter
							return BeanUtil
									.newGetter(field, jMemberDeclaration);
						}
					}
				}
			} else if (JavaUtil.isSetter(method)) {
				String fieldName = JavaUtil.getSetterFieldName(name);
				if (fieldName != null) {
					JField field = declaringClass.getField(fieldName);
					JType parmType = typeFactory.getJType(method
							.getParameterTypes()[0]);
					if (parmType == field.getType()) {
						return BeanUtil.newSetter(field, jMemberDeclaration);
					}
				}
			}
			//else create a JMethod
			return super.newJMethod(method, declaringType, name, returnType,
					modifiers, visibility, typeArguments);
		}

		@Override
		public JType newJType(QualifiedName qualifiedName,
				JTypeDeclaration declaration, JType... typeArguments) {
			return BeanTypeFactoryDelegee.this.newJType(qualifiedName,
					declaration, typeArguments);
		}
	}

	public class Candidate {
		public QualifiedName qualifiedName;
		public Class<? extends JType> typeClass;

		public Candidate(Class<? extends JType> typeClass,
				QualifiedName qualifiedName) {
			super();
			this.typeClass = typeClass;
			this.qualifiedName = qualifiedName;
		}
	}

	private Filter<Candidate> filter;

	/**
	 * creates a new factory with a default-filter the default filter accepts
	 * only Bean.class as JType this may be sufficient only in very simple
	 * scenarieos
	 */
	public BeanTypeFactoryDelegee() {
		super();
		filter = new Filter<Candidate>() {

			@Override
			public boolean includes(Candidate candidate) {
				return candidate.typeClass.equals(Bean.class);
			}
		};
	}

	public BeanTypeFactoryDelegee(Filter<Candidate> filter) {
		super();
		Condition.notNull(filter, "..filter");
		this.filter = filter;
	}

	@Override
	public boolean acceptType(Class<? extends JType> typeClass,
			QualifiedName qualifiedName) {
		return filter.includes(new Candidate(typeClass, qualifiedName));
	}

	@SuppressWarnings("unchecked")
	@Override
	public <TYPE extends JType> TYPE newJType(QualifiedName qualifiedName,
			JTypeDeclaration declaration, JType... typeArguments) {
		JClassDeclaration cDcl = copyTypeDeclaration(declaration);
		return (TYPE) new Bean(qualifiedName, typeArguments, cDcl);
	}

	@Override
	public JTypeClassVisitor getVisitor(JType... typeArguments) {
		return new BeanClassVisitor(typeFactory, typeArguments);
	}
}
